/** This program is free software: you can redistribute it and/or modify
    it under the terms of the Lesser GNU General Public License as published by
    the Free Software Foundation, either version 2.1 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    Lesser GNU General Public License for more details.

    You should have received a copy of the Lesser GNU General Public License
    along with this program. If not, see < http://www.gnu.org/licenses/>.
*/

package dz.borconlib;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import dz.borconlib.utils.Converter;

/***
 * 
 * @author Mourad TOUMI
 *
 */
public class BORcon {

	private int fPort = 0;
	private String fAdresse = "";
	private String fPass = "";

	private DatagramPacket fDatagrameReception;
	private DatagramPacket fDatagrameEnvoi;

	private DatagramSocket fDatagramSocket;

	/***
	 * Constructeur 
	 * @param pPort Numéro du port d'écoute du serveur
	 * @param pAddress Adresse IP du serveur 
	 * @param pPass Mot de pass Rcon
	 */
	public BORcon(final int pPort, final String pAddress, final String pPass) {
		fPort = pPort;
		fAdresse = pAddress;
		fPass = pPass;
	}

	/***
	 * Initialise l'API Rcon
	 * @return Retourne true si l'initialisation des propriété de l'API s'est bien déroulée.
	 */
	public boolean init() {
		try {

			final InetAddress adresse = InetAddress.getByName(fAdresse);
			fDatagrameReception = new DatagramPacket(new byte[4096], 4096);
			fDatagrameEnvoi = new DatagramPacket(new byte[4096], 4096, adresse,
					fPort);
			fDatagramSocket = new DatagramSocket();
			return true;

		} catch (UnknownHostException uhe) {
			// TODO a traiter
			System.out.println("UnknownHostException");
			return false;
		} catch (SocketException se) {
			// TODO a traiter
			System.out.println("SocketException se");
			return false;
		} catch (Exception e) {
			System.out.println("Exception");
			return false;
		}
	}

	private void initToSend() {
		fDatagrameEnvoi.setData(new byte[4096]);
	}

	private void initToReceive() {
		fDatagrameReception.setData(new byte[4096]);
	}

	/***
	 * 
	 * @param pMessage Message à envoyer à tous les joueurs
	 * @return true si succès, false sinon
	 */
	public boolean send(final String pMessage) {
		initToSend();
		final byte[] tampon = pMessage.getBytes();

		for (int i = 0; i < tampon.length; i++) {
			fDatagrameEnvoi.getData()[i] = tampon[i];

			// TODO traitement structure
		}

		try {
			fDatagramSocket.send(fDatagrameEnvoi);
		} catch (IOException ioe) {
			System.out.println("IOException");
			return false;
		}
		return true;
	}

	/***
	 * Envoie un commande Rcon au serveur
	 * @param pCommande Commande Rcon à envoyer au serveur. Voir la liste http://forums.gameservers.com/viewtopic.php?f=6&t=42538
	 * @return La réponse du serveur à la commande envoyée 
	 */
	public String sendCommande(final String pCommande) {
		try {
			Thread.sleep(2300);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		initToSend();
		initToReceive();

		byte[] result = Converter.hexStringToByteArray("ffffffff00"
				+ Converter.stringToHexa(fPass + " " + pCommande));

		fDatagrameEnvoi.setData(result);

		try {
			fDatagramSocket.connect(fDatagrameEnvoi.getSocketAddress());
			fDatagramSocket.send(fDatagrameEnvoi);
			fDatagrameReception = new DatagramPacket(new byte[4096], 4096);
			fDatagramSocket.receive(fDatagrameReception);
			
			//fDatagramSocket.close();

		} catch (IOException ioe) {
			System.out.println(ioe.getMessage());
			return "NULL";
		}

		String tmp = new String(fDatagrameReception.getData());

		final String reponse = tmp.substring(tmp.indexOf("print\n") + 5,
				tmp.indexOf(0));
		return reponse;
	}

	/***
	 * Fixer la tolérence au ping haut
	 * @param pMaxPing Ping maximum toléré par le serveur
	 */
	public void setMaxPing(int pMaxPing) {
		sendCommande("setadmindvar sv_maxPing " + pMaxPing);
	}

	/***
	 * Fixer le ping minimum accepté 
	 * @param pMinPing Ping minimum
	 */
	public void setMinPing(int pMinPing) {
		sendCommande("setadmindvar sv_minPing " + pMinPing);
	}

	/***
	 * Exclure un joueur du serveur
	 * @param pPlayer Nom Steam du joueur
	 * @param pRaison Raison de l'exclusion
	 */
	public void kick(final String pPlayer, final String pRaison) {
		sendCommande("kick " + pPlayer + " \""+pRaison+"\"");
	}

	/***
	 * Exclure un joueur du serveur
	 * @param pPlayerId GUID du joueur
	 */
	public void kickClient(final String pPlayerId) {
		sendCommande("clientkick " + pPlayerId);
	}

	/***
	 * Mise à jour du statut de la partie en cours
	 * @return Liste des status des joueurs
	 */
	public Map<String, PlayerStatus> updateStatus() {

		String[] status = sendCommande("status").split("\n");

		// TODO renseigner la map dans l'instance Application

		final Map<String, PlayerStatus> resultStatus = new HashMap<String, PlayerStatus>();

		for (int i = 4; i < status.length; i++) {
			final PlayerStatus playerStatus = new PlayerStatus();
			final String[] infos = status[i].split("\\s+");
			try {
				playerStatus.setScore(Integer.getInteger(infos[2]));
			} catch (NumberFormatException e) {
			    //TODO
			}

			try {
				playerStatus.setPing(Integer.parseInt(infos[3]));
			} catch (NumberFormatException e) {
				playerStatus.setPing(50);
			}
			
			playerStatus.setGuid(infos[4]);
			playerStatus.setName(infos[5].replaceAll("\\^[0-7]", ""));
			
			playerStatus.setNum(infos[1]);
			
			
			resultStatus.put(playerStatus.getGuid(), playerStatus);
			
		}
		return resultStatus;
	}

	/***
	 * Envoyer un message à tous les joueurs
	 * @param pMessage Message à envoyer
	 */
	public void sendMessage(final String pMessage) {
		sendCommande("say \"" + pMessage+"\"");
	}

	/***
	 * Envoyer un message à un joueur
	 * @param pName Nom Steam du joueur
	 * @param pMessage Message à envoyer
	 */
	public void sendMessage(final String pName, final String pMessage) {
		sendCommande("tell " + pName + " \"" + pMessage+"\"");
	}
}
